/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <ppltasks.h>
#include <concurrent_queue.h>
#include <baja/mru_cache.hpp>
#include <baja/transition_tracking/tracked_object.h>
#include <journal/models/interfaces.h>
#include <journal/models/db/table_interfaces.h>
#include <journal/models/page_model_factory.h>
#include <journal/models/page_cache_manager.h>

namespace journal { namespace model {

namespace b = baja;
namespace bm = baja::model;
namespace s = baja::storage;
namespace bt = baja::transition_tracking;

typedef std::function<
    void(
        b::uint32,
        std::shared_ptr<ipage_model>
        )
    > page_mru_purge_function;

class notebook_model :
    public bt::tracked_object,
    public bm::igeneric_model_observer,
    public iinternal_notebook_model,
    public inotebook_model,
    public ipage_model_observer,
    public iinternal_page_model_observer,
    public std::enable_shared_from_this<inotebook_model>
{
public:
    // constructor
    notebook_model(const b::uint32& typeToken);
    static std::shared_ptr<notebook_model> create(b::uint32 typeToken);

    // inotebook_model
    b::uint32 storageId();

    b::int32 addObserver(const std::shared_ptr<inotebook_model_observer>& observer);
    void removeObserver(const std::shared_ptr<inotebook_model_observer>& observer);
    void removeObserver(const b::int32 cookie);

    b::uint32 createPageModel();

    b::uint32 pageCount();
    std::shared_ptr<ipage_model> getPage(const b::uint32 pageNumber);
    size_t getPageNumberFromPageId(const size_t pageId);
    size_t getPageIdFromPageNumber(const size_t pageNumber);

    void startLoadingAsync();
    void cancelLoadingAsync();

    void movePage(
        size_t fromNumber,
        size_t toNumber);

    void loadPageAsync(
        const b::uint32& pageId,
        const page_level& level);

    DEBUG_MODEL_DECLARATION;

    // ipage_model_observer
    void onPageInitialized(
        const std::shared_ptr<ipage_model>& page);

    void onPageLoadLevelSet(
        const std::shared_ptr<ipage_model>& page,
        const page_level currentLevel);

    void onPhotoModelAdded(
        const std::shared_ptr<iphoto_model>& photo);

    void onPhotoModelRemoved(
        const std::shared_ptr<iphoto_model>& photo);
    
    void onPhotoModelBroughtToFront(
        const std::shared_ptr<iphoto_model>& photo);
    
    void onInkSurfaceModelReady(
        const std::shared_ptr<iink_surface_model>& inkSurface);

    void onThumbnailSaved(
        const std::shared_ptr<ipage_model>& page);

    void onPageModelPaperTypeChanged(
        const b::uint32& paperType);

    // igeneric_model_observer
    void onChildChanged(
        const b::uint32& typeToken,
        const b::uint32& genericIdentity
        );

private:

    struct observed_page
    {
        b::int32 pageCookie;
        b::int32 internalPageCookie;
        b::int32 genericPageCookie;

        observed_page() :
            pageCookie(-1),
            internalPageCookie(-1),
            genericPageCookie(-1)
        {}
    };

    struct observed_objects
    {
        // id -> observed_page
        std::map<b::uint32, observed_page> pages;
    };

    observed_objects _observedObjects;

    // load queue support
    std::recursive_mutex _mutex;

    HANDLE _notebookReadyEvent;

    page_cache_manager _pageCacheManager;

    // persistence support
    std::shared_ptr<s::iengine> _dbEngine;
    std::shared_ptr<journal::model::db::inotebook_table> _notebookTable;

    std::shared_ptr<journal::model::ipage_model_factory> _pageFactory;

    // tree data structure support
    // TRACK: bug 461 : eric: not sure how good an idea this is. If this just needs a monitor i'll make a new class...
    concurrency::concurrent_vector<std::shared_ptr<ipage_model>> _pages;

    std::map<size_t, size_t> _pageIdToPageNumberMap;

    // observers
    b::observable_source<inotebook_model_observer> _observerSource;

    std::vector<size_t> _pageOrder;

    // private methods
    b::uint32 createPageObject();
    std::shared_ptr<ipage_model> recreatePageObject(b::uint32 pageId);

    void observePage(
        const std::shared_ptr<ipage_model>& page);

    void unobservePage(
        const std::shared_ptr<ipage_model>& page);

    void attachPage(
        const std::shared_ptr<ipage_model>& page);

    void reattachPage(
        const std::shared_ptr<ipage_model>& page);

    void detachPage(
        const std::shared_ptr<ipage_model>& page);

    void ensureOnePage();
    void removePage(b::uint32 pageId);
    void recomputePageIdMap();
    void initializeFromDB();
    void loadChildrenLinks();
    void emptyLoadQueue();

    // undo redo support
    void undoChildAction(
        const std::vector<bt::transition_path_node>& path,
        const b::uint32& index,
        const std::shared_ptr<bt::itransition_parameters>& transitionParameters
        );

    void redoChildAction(
        const std::vector<bt::transition_path_node>& path,
        const b::uint32& index,
        const std::shared_ptr<bt::itransition_parameters>& transitionParameters
        );

    // helper to recover broken _pageOrder
    static bool recoverPageOrder(std::vector<b::uint32> &pageIds, std::vector<size_t> &pageOrder, std::vector<size_t> &newPageOrder);
};

}} // namespace journal::model
